package com.cardone.platform.configuration.util;

import com.cardone.common.Characters;
import com.cardone.common.cache.util.CacheUtils;
import com.cardone.common.util.MapperUtils;
import com.cardone.common.util.ReturnDataUtils;
import com.cardone.context.*;
import com.cardone.platform.configuration.dto.SiteDto;
import com.cardone.platform.configuration.service.SiteService;
import com.google.common.collect.Lists;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.Resource;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.Map;

/**
 * 站工具类
 *
 * @author yaohaitao
 */
@Slf4j
public class SiteUtils {
    /**
     * 数据位置
     */
    @Setter
    @Getter
    private static Resource dataLocation;
    /**
     * 数据路径扩展名
     */
    @Setter
    private static String dataUrlExtension = ".json";
    /**
     * 默认站
     */
    @Setter
    private static SiteDto defaultSite = new SiteDto();
    /**
     * 初始化
     */
    private static boolean initSite;
    /**
     * json路径集合
     */
    @Setter
    @Getter
    private static List<String> jsonUrlList = Lists.newArrayList("/**/*.json", "/**/*.txt");
    /**
     * 不缓存的文件
     */
    @Setter
    private static List<String> noCacheUrlList = Lists.newArrayList("/**/*.json", "/**/*.txt");

    /**
     * 上传临时目录
     */
    @Setter
    @Getter
    private static Resource uploadTempDir;
    /**
     * urlCodec
     */
    @Setter
    private static URLCodec urlCodec = new URLCodec();
    /**
     * 视图位置
     */
    @Setter
    @Getter
    private static Resource viewLocation;
    /**
     * 视图扩展名
     */
    @Setter
    private static String viewNameExtension = ".html";
    /**
     * 视图路径404
     */
    @Setter
    private static String viewNameFor404 = "error/404";
    @lombok.Setter
    private static List<com.cardone.context.function.Execution3Function<Boolean, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, Object>> preHandleFunctionList;

    public SiteUtils() {
    }

    /**
     * Callback after completion of request processing, that is, after rendering
     * the view. Will be called on any outcome of handler execution, thus allows
     * for proper resource cleanup.
     * <p/>
     * Note: Will only be called if this interceptor's {@code preHandle} method
     * has successfully completed and returned {@code true}!
     * <p/>
     * As with the {@code postHandle} method, the method will be invoked on each
     * interceptor in the chain in reverse order, so the first interceptor will be
     * the last to be invoked.
     *
     * @param request  current HTTP request
     * @param response current HTTP response
     * @param handler  handler (or {@link HandlerMethod}) that started async execution,
     *                 for type and/or instance examination
     * @param ex       exception thrown on handler execution, if any
     * @throws Exception in case of errors
     */
    public static void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) throws Exception {
        final String url = ContextHolder.getServletPath(request);

        if (com.cardone.common.util.StringUtils.matchList(SiteUtils.noCacheUrlList, url)) {
            response.setHeader("Pragma", "no-cache");
            response.addHeader("Cache-Control", "must-revalidate");
            response.addHeader("Cache-Control", "no-cache");
            response.addHeader("Cache-Control", "no-store");
            response.setDateHeader("Expires", 0);
        }
    }

    /**
     * Called instead of {@code postHandle} and {@code afterCompletion}, when the
     * a handler is being executed concurrently. Implementations may use the
     * provided request and response but should avoid modifying them in ways that
     * would conflict with the concurrent execution of the handler. A typical use
     * of this method would be to clean thread local variables.
     *
     * @param request  the current request
     * @param response the current response
     * @param handler  handler (or {@link HandlerMethod}) that started async execution,
     *                 for type and/or instance examination
     * @throws Exception in case of errors
     */
    public static void afterConcurrentHandlingStarted(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
    }

    /**
     * 填充数据
     *
     * @param request
     */
    public static void fillData(final HttpServletRequest request) {
        final String servletPath = ContextHolder.getServletPath(request);

        String[] interfaceMethodBeanNames = ContextHolder.getApplicationContext().getBeanNamesForType(com.cardone.common.web.support.InterfaceMethodSupport.class);

        if (org.apache.commons.lang3.ArrayUtils.isNotEmpty(interfaceMethodBeanNames)) {
            final String interfaceMethodBeanName = com.cardone.common.util.StringUtils.getPathForMatch(Lists.newArrayList(interfaceMethodBeanNames), servletPath);

            if (StringUtils.isNotBlank(interfaceMethodBeanName)) {
                final Object data = ContextHolder.getBean(com.cardone.common.web.support.InterfaceMethodSupport.class, interfaceMethodBeanName).execution(request);

                if (data == null) {
                    return;
                }

                if (data instanceof Map) {
                    @SuppressWarnings("unchecked")
                    final Map<String, Object> model = (Map<String, Object>) data;

                    if (CollectionUtils.isEmpty(model)) {
                        return;
                    }

                    WebUtils.exposeRequestAttributes(request, model);

                    return;
                }

                request.setAttribute(Contexts.data.name(), data);

                return;
            }
        }

        if (!log.isDebugEnabled()) {
            return;
        }

        if (SiteUtils.dataLocation == null) {
            return;
        }

        try {
            final SiteDto contextSite = SiteUtils.getContextSite(request);

            final String filename = SiteUtils.dataLocation.getFile().getAbsolutePath() + File.separator + contextSite.getStyleCode() + servletPath + SiteUtils.dataUrlExtension;

            final File jsonFile = FileUtils.getFile(filename);

            if (jsonFile == null) {
                return;
            }

            if (!jsonFile.exists()) {
                return;
            }

            final String jsonString = FileUtils.readFileToString(jsonFile);

            if (StringUtils.isBlank(jsonString)) {
                return;
            }

            final Map<String, Object> model = MapperUtils.getMapForJson(jsonString);

            if (CollectionUtils.isEmpty(model)) {
                return;
            }

            WebUtils.exposeRequestAttributes(request, model);
        } catch (final IOException e) {
            SiteUtils.log.error(e.getMessage(), e);
        }
    }

    /**
     * 获取上下文参数
     *
     * @param request request
     * @return 上下文参数
     */
    public static SiteDto getContextSite(final HttpServletRequest request) {
        String contextSiteId = ContextHolder.getSiteId();

        if (StringUtils.isNotBlank(contextSiteId)) {
            final String key = StringUtils.join(new String[]{Contexts.contextSite.name(), contextSiteId}, com.cardone.common.Characters.comma.stringValue());

            SiteDto contextSite = CacheUtils.getValue(SiteUtils.class.getName(), key, () -> {
                SiteDto site = ContextHolder.getBean(SiteService.class).findById(SiteDto.class, contextSiteId);

                SiteUtils.initSite(request, site);

                return site;
            });

            if (contextSite != null) {
                return contextSite;
            }
        }

        String serverName = (request == null) ? org.apache.commons.lang3.StringUtils.EMPTY : request.getServerName();

        String projectCode = SiteUtils.defaultSite.getProjectCode();

        String code = SiteUtils.defaultSite.getCode();

        final String key = StringUtils.join(new String[]{Contexts.contextSite.name(), serverName, projectCode, code}, com.cardone.common.Characters.comma.stringValue());

        SiteDto contextSite = CacheUtils.getValue(SiteUtils.class.getName(), key, () -> {
                    SiteDto site = ContextHolder.getBean(SiteService.class).findByServerName(SiteDto.class, serverName, projectCode, code);

                    SiteUtils.initSite(request, site);

                    return site;
                }
        );

        if (contextSite != null) {
            return contextSite;
        }

        contextSite = new SiteDto();

        SiteUtils.initSite(request, contextSite);

        return contextSite;
    }

    /**
     * 初始化站
     *
     * @param request HttpServletRequest
     * @param site    站对象
     */
    public static void initSite(final HttpServletRequest request, final SiteDto site) {
        if (site == null) {
            return;
        }

        SiteUtils.initDefaultSite(request);

//        if (CollectionUtils.isEmpty(site.getUrlWhiteList())) {
//            site.setUrlWhiteList(SiteUtils.defaultSite.getUrlWhiteList());
//        }
//
//        if (CollectionUtils.isEmpty(site.getUrlBlackList())) {
//            site.setUrlBlackList(SiteUtils.defaultSite.getUrlBlackList());
//        }
//
//        if (StringUtils.isBlank(site.getLoginUrl())) {
//            site.setLoginUrl(SiteUtils.defaultSite.getLoginUrl());
//        }
//
//        if (StringUtils.isBlank(site.getNotAuthorityUrl())) {
//            site.setNotAuthorityUrl(SiteUtils.defaultSite.getNotAuthorityUrl());
//        }

        if (StringUtils.isBlank(site.getStyleCode())) {
            site.setStyleCode(SiteUtils.defaultSite.getStyleCode());
        }

        if (MapUtils.isEmpty(site.getAttrs())) {
            site.setAttrs(SiteUtils.defaultSite.getAttrs());
        }
    }

    /**
     * 初始化
     *
     * @param request 站
     */
    @lombok.Synchronized
    private static void initDefaultSite(final HttpServletRequest request) {
        if (SiteUtils.initSite) {
            return;
        }

        SiteUtils.initSite = true;

        if (SiteUtils.defaultSite == null) {
            SiteUtils.defaultSite = new SiteDto();
        }

//        if (StringUtils.isNotBlank(SiteUtils.defaultSite.getLoginUrl()) && (request != null)) {
//            final Map<String, Object> model = Maps.newHashMap();
//
//            model.put(Contexts.contextPath.name(), request.getContextPath());
//
//            final String loginUrl = TemplateUtils.processString(SiteUtils.defaultSite.getLoginUrl(), model);
//
//            SiteUtils.defaultSite.setLoginUrl(loginUrl);
//        }

//        if (StringUtils.isNotBlank(SiteUtils.defaultSite.getNotAuthorityUrl()) && (request != null)) {
//            final Map<String, Object> model = Maps.newHashMap();
//
//            model.put(Contexts.contextPath.name(), request.getContextPath());
//
//            final String notAuthorityUrl = TemplateUtils.processString(SiteUtils.defaultSite.getNotAuthorityUrl(), model);
//
//            SiteUtils.defaultSite.setNotAuthorityUrl(notAuthorityUrl);
//        }

//        final List<String> urlBlackList = SiteUtils.defaultSite.getUrlBlackList();
//
//        SiteUtils.defaultSite.setUrlBlackList(urlBlackList);
//
//        final List<String> urlWhiteList = SiteUtils.defaultSite.getUrlWhiteList();
//
//        SiteUtils.defaultSite.setUrlWhiteList(urlWhiteList);
    }

    /**
     * 500页面或500json数据
     *
     * @param e
     * @param request
     * @param response
     * @return
     */
    public static String exception500(final Throwable e, final HttpServletRequest request, final HttpServletResponse response) {
        log.error(e.getMessage(), e);

        final String url = ContextHolder.getServletPath(request);

        if (!com.cardone.common.util.StringUtils.matchList(SiteUtils.jsonUrlList, url)) {
            request.setAttribute(Contexts.message.name(), e.getMessage());

            if (e instanceof DictionaryException) {
                DictionaryException dictionaryException = (DictionaryException) e;

                if (StringUtils.isNotBlank(dictionaryException.getUrl())) {
                    return "default" + dictionaryException.getUrl();
                }
            }

            return "default/error/500";
        }

        response.setCharacterEncoding(CharEncoding.UTF_8);
        response.setContentType("application/json");


        try (Writer out = response.getWriter()) {
            out.write(MapperUtils.toJson(ReturnDataUtils.newErrorsMap(e)));

            out.flush();
        } catch (java.io.IOException ex) {
            log.error(ex.getMessage(), ex);
        }

        return null;
    }

    /**
     * 查询
     *
     * @param mappedClass 返回类型
     * @param siteUrlCode 站路径代码
     * @return 返回对象
     */
    public static <P> P findBySiteUrlCode(final Class<P> mappedClass, final String siteUrlCode) {
        return ContextHolder.getBean(SiteService.class).findBySiteUrlCode(mappedClass, siteUrlCode);
    }

    /**
     * 查询：站
     *
     * @param siteUrlCode 站路径代码
     * @return 站
     */
    public static SiteDto findBySiteUrlCode(final String siteUrlCode) {
        return ContextHolder.getBean(SiteService.class).findBySiteUrlCode(SiteDto.class, siteUrlCode);
    }

    /**
     * 查询
     *
     * @param mappedClass 返回类型
     * @param projectCode 项目代码
     * @return 站对象集合
     */
    public static <P> List<P> findListByProjectCode(final Class<P> mappedClass, final String projectCode) {
        return ContextHolder.getBean(SiteService.class).findListByProjectCode(mappedClass, projectCode);
    }

    /**
     * 查询:站
     *
     * @param projectCode 项目代码
     * @return 站对象集合
     */
    public static List<SiteDto> findListByProjectCode(final String projectCode) {
        return ContextHolder.getBean(SiteService.class).findListByProjectCode(SiteDto.class, projectCode);
    }

    public static Map<String, Object> fromJsonForReturnData(final HttpServletRequest request, String servletPath, String method) throws IOException {
        String[] interfaceMethodBeanNames = ContextHolder.getApplicationContext().getBeanNamesForType(com.cardone.common.web.support.InterfaceMethodSupport.class);

        if (org.apache.commons.lang3.ArrayUtils.isNotEmpty(interfaceMethodBeanNames)) {
            final String interfaceMethodBeanName = com.cardone.common.util.StringUtils.getPathForMatch(Lists.newArrayList(interfaceMethodBeanNames), servletPath);

            if (StringUtils.isNotBlank(interfaceMethodBeanName)) {
                final Object data = ContextHolder.getBean(com.cardone.common.web.support.InterfaceMethodSupport.class, interfaceMethodBeanName).execution(request);

                return ReturnDataUtils.newMap(data);
            }
        }

        if (!log.isDebugEnabled()) {
            return ReturnDataUtils.newMap(null);
        }

        if (SiteUtils.dataLocation == null) {
            throw new DictionaryException("本地数据路径 dataLocation 未设置").setCode("404");
        }

        final SiteDto contextSite = SiteUtils.getContextSite(request);

        String requestMethod = StringUtils.defaultIfBlank(method, org.springframework.web.bind.annotation.RequestMethod.GET.name());

        requestMethod = StringUtils.lowerCase(requestMethod);

        final String filename = SiteUtils.dataLocation.getFile().getAbsolutePath() + File.separator + contextSite.getStyleCode() + servletPath + File.separator + requestMethod;

        final File jsonFile = FileUtils.getFile(filename);

        if ((jsonFile == null) || !jsonFile.exists()) {
            throw new DictionaryException("本地数据路径 不存在").setCode("404");
        }

        final String jsonString = FileUtils.readFileToString(jsonFile);

        if (StringUtils.isBlank(jsonString)) {
            return ReturnDataUtils.newMap(null);
        }

        return MapperUtils.getBeanForJson(Map.class, jsonString);
    }

    /**
     * 获取上下文参数
     *
     * @return 上下文参数
     */
    public static SiteDto getContextSite() {
        final HttpServletRequest request = ContextHolder.getRequest();

        return SiteUtils.getContextSite(request);
    }

    /**
     * Intercept the execution of a handler. Called after HandlerAdapter actually
     * invoked the handler, but before the DispatcherServlet renders the view. Can
     * expose additional model objects to the view via the given ModelAndView.
     * <p/>
     * DispatcherServlet processes a handler in an execution chain, consisting of
     * any number of interceptors, with the handler itself at the end. With this
     * method, each interceptor can post-process an execution, getting applied in
     * inverse order of the execution chain.
     *
     * @param request      current HTTP request
     * @param response     current HTTP response
     * @param handler      handler (or {@link HandlerMethod}) that started async execution,
     *                     for type and/or instance examination
     * @param modelAndView the {@code ModelAndView} that the handler returned (can also be
     *                     {@code null})
     * @throws Exception in case of errors
     */
    public static void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) throws Exception {
        if (modelAndView == null) {
            return;
        }

        String viewName = modelAndView.getViewName();

        SiteUtils.log.debug(viewName);

        if (StringUtils.isBlank(viewName)) {
            return;
        }

        if (StringUtils.contains(viewName, ":")) {
            return;
        }

        if (!StringUtils.startsWith(viewName, Characters.httpSeparator.stringValue())) {
            viewName = Characters.httpSeparator.stringValue() + viewName;
        }

        final SiteDto contextSite = SiteUtils.getContextSite(request);

        viewName = contextSite.getStyleCode() + viewName;

        if (!SiteUtils.isViewExists(viewName)) {
            if (!StringUtils.startsWith(SiteUtils.viewNameFor404, Characters.httpSeparator.stringValue())) {
                viewName = Characters.httpSeparator.stringValue() + SiteUtils.viewNameFor404;
            }

            viewName = contextSite.getStyleCode() + viewName;
        }

        modelAndView.setViewName(viewName);

        modelAndView.addObject(Contexts.cardoneViewName.name(), viewName);
    }

    /**
     * 视图是否存在
     *
     * @param viewName 视图名称
     * @return 是否存在
     */
    private static boolean isViewExists(final String viewName) {
        if (SiteUtils.viewLocation == null) {
            return true;
        }

        try {
            final String filename = SiteUtils.viewLocation.getFile().getAbsolutePath() + File.separator + viewName + SiteUtils.viewNameExtension;

            final File viewFile = FileUtils.getFile(filename);

            if (viewFile == null) {
                return false;
            }

            return viewFile.exists();
        } catch (final IOException e) {
            SiteUtils.log.error(e.getMessage(), e);
        }

        return true;
    }

    /**
     * 读取站点标识关于当前上下文
     *
     * @return 站点标识
     */
    public static String readIdForContext() {
        final HttpServletRequest request = ContextHolder.getRequest();

        return readIdForContext(request);
    }

    /**
     * 读取站点标识关于当前上下文
     *
     * @param request HttpServletRequest
     * @return 站点标识
     */
    public static String readIdForContext(HttpServletRequest request) {
        String contextSiteId = ContextHolder.getSiteId();

        if (StringUtils.isNotBlank(contextSiteId)) {
            return contextSiteId;
        }

        final SiteDto site = SiteUtils.getContextSite(request);

        return site.getId();
    }
}
